/**
 * com.skitsanos.data.CouchDB
 * ${DESCRIPTION}
 * @author skitsanos, info@skitsanos.com
 * @version 1.0
 * Date: 3/10/11
 */
package com.skitsanos.data
{
	import com.adobe.net.URI;
	import com.adobe.serialization.json.JSON;

	import flash.events.EventDispatcher;
	import flash.utils.ByteArray;

	import org.httpclient.HttpClient;
	import org.httpclient.HttpRequest;
	import org.httpclient.events.HttpDataEvent;
	import org.httpclient.http.Copy;
	import org.httpclient.http.Post;

	public class CouchDB extends EventDispatcher
	{
		private var _host:String = 'localhost';
		public function get host():String
		{
			return _host;
		}

		public function set host(value:String):void
		{
			_host = value;
		}

		private var _port:int = 5984;
		public function get port():int
		{
			return _port;
		}

		public function set port(value:int):void
		{
			_port = value;
		}

		private var _username:String = null;
		public function get username():String
		{
			return _username;
		}

		public function set username(value:String):void
		{
			password = value;
		}

		private var _password:String = null;
		public function get password():String
		{
			return _password;
		}

		public function set password(value:String):void
		{
			_password = value;
		}

		private var _useSsl:Boolean = false;
		public function get useSsl():Boolean
		{
			return _useSsl;
		}

		public function set useSSl(value:Boolean):void
		{
			_useSsl = value;
		}

		public function CouchDB(host:String = 'localhost', port:int = 5984, username:String = null, password:String = null)
		{
			this.host = host;
			this.port = port;
			this.username = username;
			this.password = password;
		}

		private function getUrl():String
		{
			var url:String = 'http://';
			if (useSsl)
			{
				url = 'https://';
			}

			if (username != null && password != null)
			{
				url += username + ':' + password + '@';
			}
			url += host + ':' + port;
			return url;
		}

		public function version(resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/', 'GET', null, null, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result.version);
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function getDatabases(resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/_all_dbs', 'GET', null, null, function(e:*):void
			{
				var result:Object = JSON.decode(e.target.data);
				resultHandler(result);
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function countDocuments(db:String, resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/' + db, 'GET', null, null, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result.doc_count);
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function getAllDocuments(db:String, resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/' + db + '/_all_docs', 'GET', null, null, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result);
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function getAllDesignDocuments(db:String, resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/' + db + '/_all_docs?startkey=' + encodeURI('"_design') + '&endkey=' + encodeURI('"_design0"'), 'GET', null, null, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result);
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function createDatabase(db:String, resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/' + db, 'PUT', null, null, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result);
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function deleteDatabase(db:String, resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/' + db, 'DELETE', null, null, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result);
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function createTemporaryView(db:String, map:String, reduce:String, resultHandler:Function, failureHandler:Function):void
		{
			var tempView:Object = {map: map};
			if (reduce != null)
			{
				tempView.reduce = reduce;
			}

			var url:String = getUrl() + '/' + db + '/_temp_view';

			doRequest(getUrl() + '/' + db, 'POST', null, JSON.encode(tempView), function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result);
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function createView(db:String, viewname:String, map:String, reduce:String, resultHandler:Function, failureHandler:Function):void
		{
			var tempView:Object = {map: map};
			if (reduce != null)
			{
				tempView.reduce = reduce;
			}

			var url:String = getUrl() + '/' + db + '/' + viewname;

			doRequest(url, 'POST', null, JSON.encode(tempView), function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result);
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function databaseExists(db:String, resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/' + db, 'GET', null, null, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				if (result.error != null)
				{
					resultHandler(false);
				}
				else
				{
					resultHandler(true);
				}
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function documentExists(db:String, docId:String, resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/' + db + '/' + docId, 'GET', null, null, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				if (result.error != null)
				{
					resultHandler(false);
				}
				else
				{
					resultHandler(true);
				}
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function getDocument(db:String, docId:String, resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + "/" + db + "/" + docId, 'GET', null, null, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result);
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function createDocument(db:String, doc:String, resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/' + db, 'POST', null, doc, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result);

			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function updateDocument(db:String, docId:String, doc:String, resultHandler:Function, failureHandler:Function):void
		{
			getDocument(db, docId, function(r:*):void
			{
				var tempDoc:Object = JSON.decode(doc);
				tempDoc._id = r._id;
				tempDoc._rev = r._rev;

				doRequest(getUrl() + '/' + db, 'POST', null, JSON.encode(tempDoc), function(e:*):void
				{
					var result:Object = JSON.decode(e);
					resultHandler(result);

				}, function(e:*):void
				{
					failureHandler(e);
				});
			}, function(fault:Object):void
			{
				failureHandler(fault);
			});
		}

		public function copyDocument(db:String, docId:String, newDocId:String, resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/' + db + '/' + docId, 'COPY', [
				{key: 'Destination', value: newDocId}
			], null, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result);

			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function deleteDocument(db:String, docId:String, resultHandler:Function, failureHandler:Function):void
		{
			getDocument(db, docId, function(doc:*):void
			{
				doRequest(getUrl() + '/' + db + '/' + docId + '?rev=' + doc._rev, 'DELETE', null, null, function(e:*):void
				{
					var result:Object = JSON.decode(e);
					resultHandler(result);

				}, function(e:*):void
				{
					failureHandler(e);
				});
			}, function(fault:Object):void
			{
				failureHandler(fault);
			});
		}

		/**
		 * Create Design Document. Design documents are a special type of CouchDB document
		 * that contains application code. Because it runs inside a database, the application
		 * API is highly structured.
		 * More details: http://guide.couchdb.org/draft/design.html
		 * @param db
		 * @param name
		 * @param viewName
		 * @param map
		 * @param reduce
		 * @param resultHandler
		 * @param failureHandler
		 */
		public function createDesignDocument(db:String, name:String, viewName:String, map:String, reduce:String, resultHandler:Function, failureHandler:Function):void
		{
			var doc:Object = {
				_id: '_design/' + name,
				language: 'javascript',
				views: {}
			};
			doc.views[viewName] = {map: map};
			if (reduce != null)
			{
				doc.views[viewName].reduce = reduce;
			}
			doRequest(getUrl() + '/' + db, 'POST', null, JSON.encode(doc), function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result);

			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function getView(db:String, name:String, viewName:String, resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/' + db + '/_design/' + name + '/_view/' + viewName, 'GET', null, null, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result);
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		public function getList(db:String, name:String, listName:String, viewName:String, resultHandler:Function, failureHandler:Function):void
		{
			doRequest(getUrl() + '/' + db + '/_design/' + name + '/_list/' + listName + '/' + viewName, 'GET', null, null, function(e:*):void
			{
				var result:Object = JSON.decode(e);
				resultHandler(result);
			}, function(e:*):void
			{
				failureHandler(e);
			});
		}

		private static function doRequest(url:String, method:String, headers:Array, data:String, resultHandler:Function, failureHandler:Function):void
		{
			var uri:URI = new URI(url);

			var client:HttpClient = new HttpClient();
			client.listener.onData = function(e:HttpDataEvent):void
			{
				resultHandler(e.readUTFBytes());
			};

			switch (method)
			{
				case 'GET':
					client.get(uri);
					break;

				case 'POST':
					var bytesPostBody:ByteArray = new ByteArray();
					bytesPostBody.writeUTFBytes(data);
					bytesPostBody.position = 0;

					var postRequest:HttpRequest = new Post();
					postRequest.contentType = 'application/json';
					postRequest.body = bytesPostBody;
					client.request(uri, postRequest);
					break;

				case 'PUT':
					client.put(uri, '');
					break;

				case 'DELETE':
					client.del(uri);
					break;

				case 'COPY':
					var copyRequest:HttpRequest = new Copy();
					if (headers != null)
					{
						for each(var header:Object in headers)
						{
							copyRequest.addHeader(header.key, header.value);
						}
					}
					client.request(uri, copyRequest);
					break;
			}
		}
	}
}
